package com.jinly.ryder.account.aspect;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jinly.ryder.account.service.UserInfoService;
import com.jinly.ryder.account.service.model.UserInfo;
import com.jinly.ryder.common.auth.annotation.Username;
import com.jinly.ryder.common.error.ErrorException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.*;

@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class UsernameAspect {

    @Autowired
    private UserInfoService userInfoService;

    @Around("@within(org.springframework.web.bind.annotation.RestController)" +
            "||@within(org.springframework.stereotype.Controller)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object result = pjp.proceed();
        try {
            fillUsername(result);
        } catch (Throwable e) {
            if (!(e instanceof ErrorException)) {
                log.error("fillUsername err", e);
            }
            throw e;
        }
        return result;
    }

    private void fillUsername(Object result) {
        Map<Long, List<FieldMapping>> fieldMappingMap = new HashMap<>();
        setFieldMappingMap(result, fieldMappingMap);
        if (!fieldMappingMap.isEmpty()) {
            List<UserInfo> userInfoList = userInfoService.batchQueryUsers(fieldMappingMap.keySet());
            userInfoList.forEach(u -> {
                List<FieldMapping> mappingList = fieldMappingMap.get(u.getId());
                mappingList.forEach(m -> m.setUserValue(u));
            });
        }
    }

    private void setFieldMappingMap(Object result, Map<Long, List<FieldMapping>> fieldMappingMap) {
        if (result == null) return;

        Class clazz = result.getClass();
        if (result instanceof Collection) {
            ((Collection) result).forEach(o -> setFieldMappingMap(o, fieldMappingMap));
        } else if (clazz.isArray()) {
            int len = Array.getLength(result);
            for (int i = 0; i < len; i++) {
                setFieldMappingMap(Array.get(result, i), fieldMappingMap);
            }
        } else if (
                (clazz.getPackage().getName().startsWith("com.jinly.ryder") || IPage.class.isAssignableFrom(clazz))
                        && !clazz.isEnum()
        ) {
            Field[] fs = clazz.getDeclaredFields();
            for (Field f : fs) {
                Username mapping = f.getAnnotation(Username.class);
                if (mapping != null) {
                    Object objId = getFieldValue(result, mapping.value());
                    Long id = null;
                    if (objId instanceof Long) {
                        id = (Long) objId;
                    }
                    if (objId instanceof String) {
                        id = Long.parseLong((String) objId);
                    }
                    if (id != null) {
                        if (!fieldMappingMap.containsKey(id)) {
                            fieldMappingMap.put(id, new ArrayList<>());
                        }
                        fieldMappingMap.get(id).add(new FieldMapping(result, f));
                    }
                } else {
                    setFieldMappingMap(getFieldValue(result, f.getName()), fieldMappingMap);
                }
            }
        }
    }

    private Object getFieldValue(Object result, String fieldName) {
        try {
            Field f = result.getClass().getDeclaredField(fieldName);
            f.setAccessible(true);
            return f.get(result);
        } catch (Exception e) {
            log.error("UsernameMapping error:{}", e.getMessage());
        }
        return null;
    }

    @Data
    @AllArgsConstructor
    private class FieldMapping {
        private Object o;
        private Field f;

        public void setUserValue(UserInfo user) {
            try {
                f.setAccessible(true);
                f.set(o, user.getNickname());
            } catch (Exception e) {
                log.error("UsernameMapping error:{}", e.getMessage());
            }
        }
    }
}